

#include "lua_sheet.h"

static std::mutex _mutex;
static std::map<std::string, int> global_map;

static struct globalL {
  lua_State* L;
  inline ~globalL() { lua_close(L); }
  inline  globalL() : L(luaL_newstate()) { }
} global;

#define is_continue(type, name) (type == LUA_TTABLE && *name)

/********************************************************************************/

static int new_table(lua_State* L, const char* name);

static int push_table(lua_State* L, const char* name, int level) {
  if (level == 0) {
    auto iter = global_map.find(name);
    if (iter != global_map.end()) {
      return lua_pushref(L, iter->second);
    }
  }
  char stage[1024];
  const char* p = strchr(name, '|');
  p = p ? p : strchr(name, '\0');
  memcpy(stage, name, p - name);
  stage[p - name] = '\0';
  int ty = LUA_TNONE;
  if (level > 0) {
    ty = stage[0] - '0';
    switch (ty) {
    case LUA_TNUMBER:
      lua_rawgeti(L, -1, std::stoi(stage + 1));
      break;
    default:
      lua_getfield(L, -1, stage + 1);
      break;
    }
  }
  else {
    lua_getglobal(L, stage);
  }
  int type = lua_type(L, -1);
  const char *next = (*p ? p + 1 : p);
  if (is_continue(type, next)) {
    type = push_table(L, next, level + 1);
  }
  if (level == 0) {
    if (type == LUA_TTABLE) {
      int ref = lua_ref(L, -1);
      if (ref > 0) {
        global_map[name] = ref;
      }
    }
  }
  return type;
}

static int length(lua_State* L) {
  std::unique_lock<std::mutex> lock(_mutex);
  lua_State* GL = global.L;
  lua_auto_revert revert(GL);
  push_table(GL, luaL_checkstring(L, 1), 0);
  lua_pushinteger(L, luaL_len(GL, -1));
  return 1;
}

static int iterator(lua_State* L) {
  const char* name = luaL_checkstring(L, lua_upvalueindex(1));
  std::unique_lock<std::mutex> lock(_mutex);
  lua_State* GL = global.L;
  lua_auto_revert revert(GL);
  push_table(GL, name, 0);
  lua_xmove(L, GL, 1);
  return lua_next(GL, -2) ? (lua_xmove(GL, L, 2), 2) : 0;
}

static int pairs(lua_State* L) {
  lua_pushstring(L, luaL_checkstring(L, 1));
  lua_pushcclosure(L, iterator, 1);
  return 1;
}

static int newindex(lua_State* L) {
  luaL_error(L, "%s is readonly", luaL_checkstring(L, 1));
  return 0;
}

static int check_table(lua_State* L) {
  luaL_checktype(L, 2, LUA_TTABLE);
  if (lua_getmetatable(L, 2)) {
    lua_pushliteral(L, "export");
    lua_rawget(L, -2);
    if (lua_type(L, -1) == LUA_TBOOLEAN) {
      if (lua_toboolean(L, -1)) return LUA_ERRRUN;
    }
  }
  return LUA_OK;
}

static int read_sheet(lua_State* L) {
  auto ty = lua_type(L, 3);
  lua_pushfstring(L, "%s|%d%s", luaL_checkstring(L, 1), ty, luaL_checkstring(L, 3));
  const char* name = luaL_checkstring(L, -1);
  std::unique_lock<std::mutex> lock(_mutex);
  lua_State* GL = global.L;
  lua_auto_revert revert(GL);
  int type = push_table(GL, name, 0);
  return (type == LUA_TTABLE) ? new_table(L, name) : (lua_xmove(GL, L, 1), 1);
}

static int luac_delete(lua_State* L) {
  const char* name = luaL_checkstring(L, 1);
  std::unique_lock<std::mutex> lock(_mutex);
  lua_State* GL = global.L;
  lua_auto_revert revert(GL);
  lua_pushnil(GL);
  lua_setglobal(GL, name);
  return 0;
}

static int luac_export(lua_State* L) {
  const char* name = luaL_checkstring(L, 1);
  if (lua_isnoneornil(L, 2)) {
    return luac_delete(L);
  }
  if (check_table(L) != LUA_OK) {
    luaL_error(L, "export repeat");
  }
  lua_wrap(L, 2);
  size_t size = 0;
  const char* data = luaL_checklstring(L, -1, &size);

  std::list<std::string> removed;
  std::string key(std::string(name) + "|");
  std::unique_lock<std::mutex> lock(_mutex);
  lua_State* GL = global.L;
  lua_auto_revert revert(GL);

  for (auto& iter : global_map) {
    if (iter.first.find(key) == 0) {
      lua_unref(GL, iter.second);
      removed.push_back(iter.first);
    }
  }
  for (auto& iter : removed) global_map.erase(iter);
  lua_pushlstring(GL, data, size);
  lua_unwrap(GL);
  lua_setglobal(GL, name);
  return 0;
}

static int luac_import(lua_State* L) {
  const char* name = luaL_checkstring(L, 1);
  do {
    std::unique_lock<std::mutex> lock(_mutex);
    lua_State* GL = global.L;
    lua_auto_revert revert(GL);
    if (lua_getglobal(GL, name) != LUA_TTABLE) {
      lua_pushnil(L);
      return 1;
    }
  } while (false);
  return new_table(L, name);
}

static int new_proxy(lua_State* L, lua_CFunction f) {
  lua_pushstring(L, luaL_checkstring(L, lua_upvalueindex(1)));
  lua_insert(L, 1);
  return f(L);
}

#define set_metamethod(L, name, what, f) { \
  lua_pushstring(L, name); \
  lua_pushcclosure(L, [](lua_State* L) { return new_proxy(L, f); }, 1); \
  lua_setfield(L, -2, what); \
}

static int new_table(lua_State* L, const char* name) {
  lua_newtable(L);
  lua_newtable(L);
  lua_pushboolean(L, 1);
  lua_setfield(L, -2, "export");
  set_metamethod(L, name, "__index",    read_sheet);
  set_metamethod(L, name, "__len",      length);
  set_metamethod(L, name, "__pairs",    pairs);
  set_metamethod(L, name, "__newindex", newindex);
  lua_setmetatable(L, -2);
  return 1;
}

/********************************************************************************/

LUAOS_API int luaopen_sheet(lua_State* L) {
  const luaL_Reg methods[] = {
    { "export",   luac_export },
    { "import",   luac_import },
    { NULL,       NULL        }
  };
  return new_module(L, "table", methods);
}

/********************************************************************************/
